package com.apobates.forum.orize.core;

import com.apobates.forum.orize.core.plug.AntPathMatcher;
import java.io.Serializable;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 配置不需要验证的请求
 * @author xiaofanku@live.cn
 * @since 20210822
 */
public final class OrizeAuthIgnoreConfig implements Serializable {
    private final List<String> domain;
    private final List<String> mediaType;
    private final List<String> path;
    //是否是默认的配置
    private final boolean defaultConfig;

    private OrizeAuthIgnoreConfig(List<String> domain, List<String> mediaType, List<String> path, boolean isCustomConfig) {
        this.domain = (null!=domain)?Collections.unmodifiableList(domain):new ArrayList<>();
        this.mediaType = (null!=mediaType)?Collections.unmodifiableList(mediaType):new ArrayList<>();
        this.path = (null!=path)?Collections.unmodifiableList(path):new ArrayList<>();
        this.defaultConfig = isCustomConfig;
    }

    /**
     * 获取一个默认的配置
     * @return
     */
    public static OrizeAuthIgnoreConfig defaultInstance(){
        return new OrizeAuthIgnoreConfig(null, null, null, true);
    }

    /**
     * 设置忽略验证的域名
     * @param domain 域名,例如: apobates.com
     * @return
     */
    public OrizeAuthIgnoreConfig setIgnoreDomain(String... domain){
        return new OrizeAuthIgnoreConfig(Arrays.asList(domain), mediaType, path, false);
    }

    /**
     * 设置忽略验证的资源类型
     * @param mediaType 媒体类型,例如: js, jpg, gif
     * @return
     */
    public OrizeAuthIgnoreConfig setIgnoreMediaType(String... mediaType){
        return new OrizeAuthIgnoreConfig(domain, Arrays.asList(mediaType), path, false);
    }

    /**
     * 设置忽略验证的地址
     * @param requestPath 请求路径,例如: /login, /uid.png(头像地址), /offline
     * @return
     */
    public OrizeAuthIgnoreConfig setIgnoreRequestPath(String... requestPath){
        return new OrizeAuthIgnoreConfig(domain, mediaType, Arrays.asList(requestPath), false);
    }

    /**
     * 返回忽略验证的域名
     * @return
     */
    public List<String> getDomain() {
        return domain;
    }

    /**
     * 返回忽略验证的资源类型
     * @return
     */
    public List<String> getMediaType() {
        return mediaType;
    }

    /**
     * 返回忽略验证的地址
     * @return
     */
    public List<String> getPath() {
        return path;
    }

    /**
     * 是否需要忽略请求
     * @return true不需要再进行验证了(表明符合忽略的设置),false继续进行检查
     */
    public boolean isIgnoreRequest(URL httpRequestURL){
        final String reqDomain = httpRequestURL.getHost();
        final String reqPath = httpRequestURL.getPath();
        final Optional<String> reqMediaType = getExtFileType(reqPath);
        //替掉默认的值
        final List<String> cusIgnoreDomain = getDomain();
        final List<String> cusIgnoreMediaType = getMediaType();
        final List<String> cusIgnorePath = getPath();

        //再进行: 忽略:true, 不忽略:false
        boolean ignoreDomain=matchIgnoreDomain(cusIgnoreDomain, reqDomain);
        boolean ignorePath=matchIgnorePath(cusIgnorePath, reqPath);
        boolean ignoreExt = matchIgnoreExt(cusIgnoreMediaType, reqMediaType);
        return ignoreDomain || ignorePath || ignoreExt;
    }

    //验证忽略的域名, 是忽略的返回true
    private boolean matchIgnoreDomain(List<String> cusIgnoreDomain, String reqDomain){
        boolean ignoreDomain=!cusIgnoreDomain.isEmpty();
        if(ignoreDomain){
            //: *.a.com, a.com, cdn.a.com
            return cusIgnoreDomain.stream().map(strp->matchComposeDomain(strp, reqDomain)).reduce(Boolean.FALSE, Boolean::logicalOr);
        }
        return false;
    }

    //验证忽略的路径, 是忽略的返回true
    private boolean matchIgnorePath(List<String> cusIgnorePath, String reqPath){
        boolean ignorePath=!cusIgnorePath.isEmpty();
        if(ignorePath){
            final AntPathMatcher apm = new AntPathMatcher();
            //: /static, /static/*, /static/*/*.css, /static/**
            return cusIgnorePath.stream().map(strp->matchAntPath(strp, reqPath, apm)).reduce(Boolean.FALSE, Boolean::logicalOr);
        }
        return false;
    }

    //验证忽略的扩展名, 是忽略的返回true
    private boolean matchIgnoreExt(List<String> cusIgnoreMediaType, Optional<String> reqMediaType){
        boolean ignoreExt=!cusIgnoreMediaType.isEmpty();
        if(ignoreExt){
            return reqMediaType.isPresent() && cusIgnoreMediaType.contains(reqMediaType.get());
        }
        return false;
    }

    private static boolean matchRegDomain(String pattern, String str){
        if(pattern.indexOf("*") == -1){
            return false;
        }
        String tmp = pattern.replaceAll("\\*", "(.?)*");
        Pattern p = Pattern.compile(tmp);
        Matcher matcher = p.matcher(str);
        return matcher.find();
    }

    private static boolean matchComposeDomain(String pattern, String str){
        if(pattern.indexOf("*") != -1){
            return matchRegDomain(pattern, str);
        }else{
            if(pattern.length()>str.length()){
                return false;
            }
            return str.endsWith(pattern);
        }
    }

    private static boolean matchAntPath(String pattern, String str, AntPathMatcher apm){
        if(pattern.indexOf("*") != -1){
            return apm.matches(pattern, str);
        }else{
            return str.startsWith(pattern);
        }
    }

    /**
     * 是否是自定义配置
     * @return true是/false默认配置(非自定义)
     */
    public boolean isCustomConfig(){
        return !defaultConfig;
    }

    //获取文件扩展名
    private static Optional<String> getExtFileType(String fileName){
        try {
            String optExt = fileName.substring(fileName.lastIndexOf(".") + 1);
            return Optional.ofNullable(optExt);
        }catch (Exception e){ }
        return Optional.empty();
    }
}
